/**
 * Pre-publish script for Ant Design
 *
 * Environment Variables:
 * - SKIP_CI_CHECK: Set to any truthy value to skip remote CI status check
 *   Usage: SKIP_CI_CHECK=1 npm run prepublishOnly
 */

import fs from 'node:fs';
import runScript from '@npmcli/run-script';
import { Octokit } from '@octokit/rest';
import AdmZip from 'adm-zip';
import axios from 'axios';
import chalk from 'chalk';
import dotnev from 'dotenv';
import Spinnies from 'spinnies';

import checkRepo from './check-repo';

dotnev.config({ override: true });

const { Notification: Notifier } = require('node-notifier');
const simpleGit = require('simple-git');

const spinner = { interval: 80, frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] };
const spinnies = new Spinnies({ spinner });

let spinniesId = 0;

// `spinnies` 为按条目进度，需要做简单的封装变成接近 `ora` 的形态
const showMessage = (
  message: string,
  status?: 'succeed' | 'fail' | 'spinning' | 'non-spinnable' | 'stopped' | true,
  uniqueTitle?: string,
) => {
  if (!status) {
    spinnies.add(`info-${spinniesId}`, {
      text: message,
      status: 'non-spinnable',
    });
    spinniesId += 1;
  } else {
    const mergedId = uniqueTitle || `msg-${spinniesId}`;
    let mergedMessage = uniqueTitle ? `${uniqueTitle} ${message}` : message;

    // `spinnies` 对中文支持有 bug，长度会按中文一半计算。我们翻个倍修复一下。
    mergedMessage = `${mergedMessage}${' '.repeat(mergedMessage.length)}`;

    const existSpinner = spinnies.pick(mergedId);
    if (!existSpinner) {
      spinnies.add(mergedId, {
        text: '',
      });
    }

    if (status === 'succeed' || status === 'fail' || status === 'stopped') {
      spinnies.update(mergedId, {
        text: mergedMessage,
        status,
      });
      spinniesId += 1;
    } else {
      spinnies.update(mergedId, {
        text: mergedMessage,
        status: status === true ? 'spinning' : status,
      });
    }
  }
};

process.on('SIGINT', () => {
  process.exit(1);
});

const toMB = (bytes: number) => (bytes / 1024 / 1024).toFixed(2);

async function downloadArtifact(msgKey: string, url: string, filepath: string, token?: string) {
  const headers: Record<string, string> = {};
  if (token) {
    headers.Authorization = `token ${token}`;
  }

  const response = await axios.get(url, {
    headers,
    responseType: 'arraybuffer',
    onDownloadProgress: (progressEvent) => {
      const { loaded, total = 0 } = progressEvent;

      showMessage(
        `下载进度 ${toMB(loaded)}MB/${toMB(total)}MB (${((loaded / total) * 100).toFixed(2)}%)`,
        true,
        msgKey,
      );
    },
  });

  fs.writeFileSync(filepath, Buffer.from(response.data));

  return filepath;
}

const runPrePublish = async () => {
  await checkRepo();
  showMessage(chalk.black.bgGreenBright('本次发布将跳过本地 CI 检查，远程 CI 通过后方可发布'));
  const git = simpleGit();
  const octokit = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
  const { current: currentBranch } = await git.branch();

  showMessage(`正在拉取远程分支 ${currentBranch}`, true);
  await git.pull('origin', currentBranch);
  showMessage(`成功拉取远程分支 ${currentBranch}`, 'succeed');
  showMessage(`正在推送本地分支 ${currentBranch}`, true);
  await git.push('origin', currentBranch);
  showMessage(`成功推送远程分支 ${currentBranch}`, 'succeed');
  showMessage(`已经和远程分支保持同步 ${currentBranch}`, 'succeed');

  const { latest } = await git.log();
  const sha = process.env.TARGET_SHA || latest.hash;

  showMessage(`找到本地最新 commit:`, 'succeed');
  showMessage(chalk.cyan(`  hash: ${sha}`));
  showMessage(chalk.cyan(`  date: ${latest.date}`));
  showMessage(chalk.cyan(`  message: ${latest.message}`));
  showMessage(chalk.cyan(`  author_name: ${latest.author_name}`));

  const owner = 'ant-design';
  const repo = 'ant-design';

  // Check if CI check should be skipped
  if (process.env.SKIP_CI_CHECK) {
    showMessage(`检测到 SKIP_CI_CHECK 环境变量，跳过远程分支 CI 状态检查`, 'succeed');
  } else {
    showMessage(`开始检查远程分支 ${currentBranch} 的 CI 状态`, true);

    const failureUrlList: string[] = [];

    const { data } = await octokit.rest.repos.getCombinedStatusForRef({
      owner,
      repo,
      ref: sha,
    });

    showMessage(`远程分支 CI 状态：${data.state}`, 'succeed');
    if (data.state === 'failure') {
      showMessage(
        chalk.bgRedBright('远程分支 CI 执行异常，无法继续发布，请尝试修复或重试'),
        'fail',
      );
      showMessage(`  点此查看状态：https://github.com/${owner}/${repo}/commit/${sha}`);

      failureUrlList.forEach((url) => {
        showMessage(`  - ${url}`);
      });

      process.exit(1);
    }

    if (data.state === 'pending') {
      showMessage(chalk.bgRedBright('远程分支 CI 还在执行中，请稍候再试'), 'fail');
      showMessage(`  点此查看状态：https://github.com/${owner}/${repo}/commit/${sha}`);
      process.exit(1);
    }

    if (data.state !== 'success') {
      showMessage(chalk.bgRedBright('远程分支 CI 状态异常'), 'fail');
      showMessage(`  点此查看状态：https://github.com/${owner}/${repo}/commit/${sha}`);
      process.exit(1);
    }

    showMessage(`远程分支 CI 已通过`, 'succeed');
  }
  // clean up
  await runScript({ event: 'clean', path: '.', stdio: 'inherit' });
  showMessage(`成功清理构建产物目录`, 'succeed');

  // 从 github artifact 中下载产物
  const downloadArtifactPromise = Promise.resolve().then(async () => {
    showMessage('开始查找远程分支构建产物', true, '[Github]');

    const {
      data: { workflow_runs },
    } = await octokit.rest.actions.listWorkflowRunsForRepo({
      owner,
      repo,
      head_sha: sha,
      per_page: 100,
      exclude_pull_requests: true,
      event: 'push',
      status: 'completed',
      conclusion: 'success',
      head_branch: currentBranch,
    });
    const testWorkflowRun = workflow_runs.find((run) => run.name === '✅ test');
    if (!testWorkflowRun) {
      throw new Error('找不到远程构建工作流');
    }

    const {
      data: { artifacts },
    } = await octokit.actions.listWorkflowRunArtifacts({
      owner,
      repo,
      run_id: testWorkflowRun?.id || 0,
    });
    const artifact = artifacts.find((item) => item.name === 'build artifacts');
    if (!artifact) {
      throw new Error('找不到远程构建产物');
    }

    showMessage(`准备从远程分支下载构建产物`, true, '[Github]');
    const { url } = await octokit.rest.actions.downloadArtifact.endpoint({
      owner,
      repo,
      artifact_id: artifact.id,
      archive_format: 'zip',
    });

    // 返回下载后的文件路径
    return downloadArtifact('[Github]', url, 'artifacts.zip', process.env.GITHUB_ACCESS_TOKEN);
  });
  downloadArtifactPromise
    .then(() => {
      showMessage(`成功下载构建产物`, 'succeed', '[Github]');
    })
    .catch((e: Error) => {
      showMessage(chalk.bgRedBright(e.message), 'fail', '[Github]');
    });

  // 从 OSS 下载产物
  const downloadOSSPromise = Promise.resolve().then(async () => {
    const url = `https://antd-visual-diff.oss-accelerate.aliyuncs.com/${sha}/oss-artifacts.zip`;

    showMessage(`准备从远程 OSS 下载构建产物`, true, '[OSS]');

    // 返回下载后的文件路径
    return downloadArtifact('[OSS]', url, 'oss-artifacts.zip');
  });
  downloadOSSPromise
    .then(() => {
      showMessage(`成功下载构建产物`, 'succeed', '[OSS]');
    })
    .catch((e: Error) => {
      showMessage(chalk.bgRedBright(e.message), 'fail', '[OSS]');
    });

  // 任意一个完成，则完成
  let firstArtifactFile: string;

  try {
    // @ts-ignore
    firstArtifactFile = await Promise.any([downloadArtifactPromise, downloadOSSPromise]);
  } catch (error) {
    showMessage(
      chalk.bgRedBright(`下载失败 ${error}，请确认你当前 ${sha.slice(0, 6)} 位于 master 分支中`),
      'fail',
    );
    process.exit(1);
  }

  showMessage(`成功从远程分支下载构建产物`, 'succeed');

  // unzip
  showMessage(`正在解压构建产物`, true);
  const zip = new AdmZip(firstArtifactFile);
  zip.extractAllTo('./', true);
  showMessage(`成功解压构建产物`, 'succeed');
  await runScript({ event: 'test:dekko', path: '.', stdio: 'inherit' });
  await runScript({ event: 'test:package-diff', path: '.', stdio: 'inherit' });
  showMessage(`文件检查通过，准备发布！`, 'succeed');

  try {
    await new Notifier().notify({
      title: '✅ 准备发布到 npm',
      message: '产物已经准备好了，快回来输入 npm 校验码了！',
      sound: 'Crystal',
    });
  } catch (e) {
    console.log('通知发送失败', e);
  }

  process.exit(0);
};

runPrePublish();
